package com.sdweizan.sdk.bytedance.api.impl;

import com.alibaba.fastjson.TypeReference;
import com.sdweizan.sdk.bytedance.api.BdMiniAppService;
import com.sdweizan.sdk.bytedance.api.BdOpenService;
import com.sdweizan.sdk.bytedance.bean.oauth2.BdAccessTokenRequest;
import com.sdweizan.sdk.bytedance.bean.oauth2.BdAccessTokenResponse;
import com.sdweizan.sdk.bytedance.config.BdConfigStorage;
import com.sdweizan.sdk.bytedance.constants.BdConstants;
import com.sdweizan.sdk.bytedance.error.BdError;
import com.sdweizan.sdk.bytedance.error.BdErrorEnum;
import com.sdweizan.sdk.bytedance.error.BdErrorException;
import com.sdweizan.sdk.bytedance.error.BdRuntimeException;
import com.sdweizan.sdk.bytedance.util.HttpClientGetRequestExecutor;
import com.sdweizan.sdk.bytedance.util.JsonUtils;
import com.sdweizan.sdk.bytedance.util.RequestExecutor;
import com.sdweizan.sdk.bytedance.util.SignUtils;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.StringUtils;
import org.apache.http.client.utils.URIBuilder;

import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

@Slf4j
public class BdServiceOpenImpl extends BdServiceBaseImpl implements BdOpenService {
  @Getter private final BdLinkService bdLinkService = new BdLinkService(this);

  @Getter
  private final BdGeneralTransactionService bdGeneralTransactionService =
      new BdGeneralTransactionService(this);

  public BdServiceOpenImpl(BdMiniAppService bdMiniAppService) {
    super(bdMiniAppService);
  }

  protected <T> T executeInternal(
      String requestUri,
      Map<String, Object> requestBody,
      RequestExecutor<T, Map<String, Object>> executor,
      boolean doNotAutoRefresh)
      throws BdErrorException {
    BdConfigStorage configStorage = bdMiniAppService.getConfigStorage();
    String accessToken = getAccessToken();
    Map<String, String> requestHeader = new HashMap<>();
    requestHeader.put("access-token", accessToken);
    try {
      T result = executor.execute(requestUri, requestBody, requestHeader);
      log.info("\n【请求地址】: {}\n【请求参数】：{}\n【响应数据】：{}", requestUri, requestBody, result);
      return result;
    } catch (BdErrorException e) {
      BdError error = e.getError();
      if (BdConstants.ACCESS_TOKEN_ERROR_CODES.contains(error.getErrorCode())) {
        // 强制设置wxMpConfigStorage它的access token过期了，这样在下一次请求里就会刷新access token
        Lock lock = configStorage.getAccessTokenLock();
        lock.lock();
        try {
          if (StringUtils.equals(configStorage.getOpenAccessToken(), accessToken)) {
            configStorage.expireAccessToken();
          }
        } catch (Exception ex) {
          configStorage.expireAccessToken();
        } finally {
          lock.unlock();
        }
        if (configStorage.isAutoRefreshToken() && !doNotAutoRefresh) {
          log.info(
              "即将重新获取新的access_token，错误代码：{}，错误信息：{}", error.getErrorCode(), error.getErrorMsg());
          // 下一次不再自动重试
          // 当小程序误调用第三方平台专属接口时,第三方无法使用小程序的access token,如果可以继续自动获取token会导致无限循环重试,直到栈溢出
          return executeInternal(requestUri, requestBody, executor, true);
        }
      }

      if (!error.getErrorCode().equals(BdErrorEnum.ENUM_SUCCESS.getCode())) {
        log.error("\n【请求地址】: {}\n【请求参数】：{}\n【错误信息】：{}", requestUri, requestBody, error);
        throw new BdErrorException(error, e);
      }
      return null;
    } catch (IOException e) {
      log.error("\n【请求地址】: {}\n【请求参数】：{}\n【异常信息】：{}", requestUri, requestBody, e.getMessage());
      throw new BdErrorException(e);
    }
  }

  @Override
  public String getAccessToken() throws BdErrorException {
    return getAccessToken(false);
  }

  @Override
  public String getAccessToken(boolean forceRefresh) throws BdErrorException {
    final BdConfigStorage configStorage = bdMiniAppService.getConfigStorage();
    if (!configStorage.isOpenAccessTokenExpired() && !forceRefresh) {
      return configStorage.getOpenAccessToken();
    }

    Lock lock = configStorage.getOpenAccessTokenLock();
    boolean locked = false;
    try {
      do {
        locked = lock.tryLock(100, TimeUnit.MILLISECONDS);
        if (!forceRefresh && !configStorage.isOpenAccessTokenExpired()) {
          return configStorage.getOpenAccessToken();
        }
      } while (!locked);

      BdAccessTokenResponse bdAccessTokenResponse =
          new HttpClientGetRequestExecutor<>(new TypeReference<BdAccessTokenResponse>() {})
              .execute(
                  BdConstants.OPEN_HOST + "/oauth/client_token",
                  JsonUtils.transMap(
                      BdAccessTokenRequest.builder()
                          .appId(configStorage.getAppId())
                          .secret(configStorage.getSecret())
                          .grantType("client_credential")
                          .build()));
      Integer errorCode = bdAccessTokenResponse.getData().getErrorCode();
      if (errorCode != null && errorCode == 0) {
        configStorage.updateOpenAccessToken(bdAccessTokenResponse);
        return bdAccessTokenResponse.getData().getAccessToken();
      } else {
        log.info("获取access_token失败，原因{}", bdAccessTokenResponse.getData().getDescription());
        return null;
      }
    } catch (Exception e) {
      throw new BdRuntimeException(e);
    } finally {
      if (locked) lock.unlock();
    }
  }
}
